package uml.FileIO;

import java.awt.Container;
import java.awt.Point;
import java.awt.Stroke;
import java.util.ArrayList;
import java.util.Scanner;

import uml.FileIO.FileScanner.ScanLine;
import uml.eventsystem.events.UMLEventId;
import uml.objects.AnchorPoint;
import uml.objects.ArrowEnd;
import uml.objects.UMLObject;
import uml.util.UMLConstants;
import uml.view.factory.ContainerFactory;
import uml.view.factory.UMLObjectFactory;

public class FileParser
{
	private UMLObject[] m_objects;
	private ArrayList<AnchorPointPair> m_anchorPointData;// for second pass of
														 // anchorPoints
	private int m_objectCount = 0;// for keeping the indexes the same

	private FileParser ()
	{
		m_anchorPointData = new ArrayList<AnchorPointPair> ();
	}

	public static ArrayList<UMLObject> parseTokens (ArrayList<ArrayList<ScanLine>> tokens)
	{
		FileParser parser = new FileParser ();
		parser.parseFile (tokens);
		ArrayList<UMLObject> objects = new ArrayList<UMLObject> ();
		for (UMLObject object : parser.m_objects)
			objects.add (object);
		for (AnchorPointPair pair : parser.m_anchorPointData)
			objects.add (pair.point);
		return objects;
	}

	public AnchorPointPair parseAnchorPointBlock (ArrayList<ScanLine> block)
	{
		UMLAnchorPointData arrowData = new UMLAnchorPointData ();
		for (ScanLine line : block)
			arrowData.parseLine (line);
		AnchorPoint anchor = new AnchorPoint (arrowData.location, null, getStroke (arrowData.stroke),
				getArrowEnd (arrowData.arrowEnd));
		return new AnchorPointPair (arrowData, anchor);
	}

	public void parseFile (ArrayList<ArrayList<ScanLine>> tokens)
	{
		m_objects = new UMLObject[tokens.size ()];
		for (ArrayList<ScanLine> block : tokens)
			parseBlock (block);
		parseArrowData ();
	}

	/**
	 * Parse a block of text
	 */
	
	public void parseBlock (ArrayList<ScanLine> block)
	{
		FileToken objectType = null;
		if (!block.isEmpty ())
			objectType = block.get (0).m_lexeme;
		if (objectType == FileToken.CLASS)
			m_objects[m_objectCount] = parseClassBlock (block);
		else if (objectType == FileToken.ANCHOR)
			m_anchorPointData.add (parseAnchorPointBlock (block));
		++m_objectCount;
	}

	/**
	 * Parse a block of text to be interpreted as a class object
	 * @param block
	 * @return
	 */
	
	public UMLObject parseClassBlock (ArrayList<ScanLine> block)
	{
		UMLClassBoxData classData = new UMLClassBoxData ();
		for (ScanLine line : block)
			classData.parseLine (line);

		String[] text = new String[classData.text.size ()];
		for (int i = 0; i < classData.text.size (); ++i)
		{
			text[i] = classData.text.get (i).toString ();
		}
		UMLObject umlObject = ContainerFactory.createContainer (classData.event, text);
		if (umlObject != null)
			umlObject.getComponent ().setLocation (classData.location);
		return umlObject;
	}

	/**
	 * Interprets a block of text as an arrow
	 */
	
	public void parseArrowData ()
	{
		for (AnchorPointPair pair : m_anchorPointData)
		{
			for (int index : pair.data.siblingIndexes)
				pair.point.addSibling (m_anchorPointData.get (index).point);
			if (pair.data.containerIndex >= 0)
				pair.point
						.setContextWithoutConversion ((Container) m_objects[pair.data.containerIndex].getComponent ());
			// m_objects.add(pair.point);
		}
	}

	public Stroke getStroke (FileToken token)
	{
		switch (token)
		{
		case REGULAR_LINE:
			return UMLConstants.REGULAR_LINE;
		case DASHED_LINE:
			return UMLConstants.DASHED_LINE;
		default:
			return null;
		}
	}

	public ArrowEnd getArrowEnd (FileToken token)
	{
		switch (token)
		{
		case WHITEDIAMOND:
			return UMLConstants.WHITE_DIAMOND;
		case BLACKDIAMOND:
			return UMLConstants.BLACK_DIAMOND;
		case BLACKHEAD:
			return UMLConstants.BLACK_ARROW_HEAD;
		case WHITEHEAD:
			return UMLConstants.WHITE_ARROW_HEAD;
		default:
			return null;
		}
	}

	/**
	 * Data representation of an anchor point pair
	 * @author ntobetz
	 *
	 */
	
	private class AnchorPointPair
	{
		public UMLAnchorPointData data;
		public AnchorPoint point;

		public AnchorPointPair (UMLAnchorPointData d, AnchorPoint p)
		{
			data = d;
			point = p;
		}
	}
	
	/**
	 * Data abstraction for anchor points
	 * 
	 *
	 */

	private class UMLAnchorPointData
	{
		public UMLEventId event;
		public int containerIndex;
		public ArrayList<Integer> siblingIndexes;
		public Point location;
		public FileToken arrowEnd;
		public FileToken stroke;

		UMLAnchorPointData ()
		{
			event = null;
			containerIndex = -1;
			siblingIndexes = new ArrayList<Integer> ();
			location = new Point (0, 0);
			arrowEnd = FileToken.NULL;
		}

		/**
		 * parse a line of text an switch on tokens
		 * @param line
		 */
		
		public void parseLine (ScanLine line)
		{
			switch (line.m_lexeme)
			{
			case EVENTID:
				setEventId (line.m_params);
				break;
			case CONTAINER:
				setContainerIndex (line.m_params);
				break;
			case SIBLINGS:
				setSiblingIndexes (line.m_params);
				break;
			case LOCATION:
				setLocation (line.m_params);
				break;
			case WHITEHEAD:
				arrowEnd = line.m_lexeme;
				break;
			case BLACKHEAD:
				arrowEnd = line.m_lexeme;
				break;
			case WHITEDIAMOND:
				arrowEnd = line.m_lexeme;
				break;
			case BLACKDIAMOND:
				arrowEnd = line.m_lexeme;
				break;
			case REGULAR_LINE:
				stroke = line.m_lexeme;
				break;
			case DASHED_LINE:
				stroke = line.m_lexeme;
			default:
				break;
			}
		}

		/**
		 * Store the location of a point
		 * @param params
		 */
		
		private void setLocation (String params)
		{
			ArrayList<Integer> coordinates = new ArrayList<Integer> ();
			Scanner scanner = new Scanner (params);
			while (scanner.hasNext ())
				if (scanner.hasNextInt ())
					coordinates.add (scanner.nextInt ());
			if (coordinates.size () > 1)
				location = new Point (coordinates.get (0), coordinates.get (1));
		}

		/**
		 * Store sibling indexes so we can construct the arrow
		 * @param params
		 */
		
		private void setSiblingIndexes (String params)
		{
			Scanner scanner = new Scanner (params);
			while (scanner.hasNext ())
			{
				if (scanner.hasNextInt ())
				{
					siblingIndexes.add (scanner.nextInt ());
				}
				else
				{
					scanner.next ();
				}
			}
		}

		private void setContainerIndex (String params)
		{
			Scanner scanner = new Scanner (params);
			while (scanner.hasNextInt ())
				if (scanner.hasNextInt ())
				{
					containerIndex = scanner.nextInt ();
					return;
				}
				else
					scanner.next ();
		}

		private void setEventId (String params)
		{
			Scanner scanner = new Scanner (params);
			while (scanner.hasNext ())
			{
				String temp = scanner.next ();
				for (UMLEventId eventId : UMLEventId.values ())
					if (eventId.toString ().equals (temp))
					{
						event = eventId;
						return;
					}
			}
		}
	}

	/**
	 * Data abstraction for the UMLClass generated from parse
	 *
	 */
	
	private class UMLClassBoxData
	{
		UMLEventId event;
		Point location;
		ArrayList<StringBuffer> text;

		public UMLClassBoxData ()
		{
			text = new ArrayList<StringBuffer> ();
			location = new Point ();
			event = null;
		}

		int index = -1;

		public void parseLine (ScanLine line)
		{
			switch (line.m_lexeme)
			{
			case EVENTID:
				setEventId (line.m_params);
				break;
			case LOCATION:
				setLocation (line.m_params);
				break;
			case TEXT:
				setText (new StringBuffer (line.m_params));
				index++;
				break;
			case NEWLINE:
				text.get (index).append ("\n" + line.m_params);
				break;
			}
		}

		/**
		 * Set the text in the class data abstraction
		 * @param params
		 */
		
		private void setText (StringBuffer params)
		{
			text.add (params);
		}
		
		/**
		 * Set the location of the class in the data abstraction
		 * @param params
		 */

		private void setLocation (String params)
		{
			ArrayList<Integer> coordinates = new ArrayList<Integer> ();
			Scanner scanner = new Scanner (params);
			while (scanner.hasNext ())
				if (scanner.hasNextInt ())
					coordinates.add (scanner.nextInt ());
			if (coordinates.size () > 1)
				location = new Point (coordinates.get (0), coordinates.get (1));
		}

		private void setEventId (String params)
		{
			Scanner scanner = new Scanner (params);
			while (scanner.hasNext ())
			{
				String temp = scanner.next ();
				for (UMLEventId eventId : UMLEventId.values ())
					if (eventId.toString ().equals (temp))
					{
						event = eventId;
						return;
					}
			}
		}
	}
}
